<!doctype html>
<meta charset=utf-8>
<title>IndexedDB: Exceptions in extracting keys from values (ES bindings)</title>
<meta name="help" href="https://w3c.github.io/IndexedDB/#extract-key-from-value">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/support.js"></script>
<script>

indexeddb_test(
  (t, db) => {
    db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.c'});
  },
  (t, db) => {
    const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'});
    assert_throws_dom('DataError', () => {
      tx.objectStore('store').put({a: {b: "foo"}});
    }, 'Put should throw if key can not be inserted at key path location.');
    t.done();
  },
  'The last element of keypath is validated'
);

const err = Error();
err.name = 'getter';

function throwingGetter() {
  throw err;
}

indexeddb_test(
  function(t, db) {
    const o = {};
    Object.defineProperty(o, 'throws', {get: throwingGetter,
        enumerable: false, configurable: true});

    // Value should be cloned before key path is evaluated,
    // and non-enumerable getter will be ignored. The clone
    // will have no such property, so key path evaluation
    // will fail.
    const s1 = db.createObjectStore('s1', {keyPath: 'throws'});
    assert_throws_dom('DataError', () => {
      s1.put(o);
    }, 'Key path failing to resolve should throw');

    // Value should be cloned before key path is evaluated,
    // and non-enumerable getter will be ignored. The clone
    // will have no such property, so key path evaluation
    // will fail.
    const s2 = db.createObjectStore('s2', {keyPath: 'throws.x'});
    assert_throws_dom('DataError', () => {
      s2.put(o);
    }, 'Key path failing to resolve should throw');

    // Value should be cloned before key path is evaluated,
    // and non-enumerable getter will be ignored. The clone
    // will have no such property, so generated key can be
    // inserted.
    const s3 = db.createObjectStore('s3',
        {keyPath: 'throws', autoIncrement: true});
    assert_class_string(s3.put(o), 'IDBRequest',
        'Key injectability test at throwing getter should succeed');

    // Value should be cloned before key path is evaluated,
    // and non-enumerable getter will be ignored. The clone
    // will have no such property, so intermediate object
    // and generated key can be inserted.
    const s4 = db.createObjectStore('s4',
        {keyPath: 'throws.x', autoIncrement: true});
    assert_class_string(s4.put(o), 'IDBRequest',
        'Key injectability test past throwing getter should succeed');
  },
  (t, db) => {
    t.done();
  },
  'Key path evaluation: Exceptions from non-enumerable getters'
);

indexeddb_test(
  function(t, db) {
    const o = {};
    Object.defineProperty(o, 'throws', {get: throwingGetter,
        enumerable: true, configurable: true});

    // Value should be cloned before key path is evaluated,
    // and enumerable getter will rethrow.
    const s1 = db.createObjectStore('s1', {keyPath: 'throws'});
    assert_throws_exactly(err, () => {
      s1.put(o);
    }, 'Key path resolving to throwing getter rethrows');

    // Value should be cloned before key path is evaluated,
    // and enumerable getter will rethrow.
    const s2 = db.createObjectStore('s2', {keyPath: 'throws.x'});
    assert_throws_exactly(err, () => {
      s2.put(o);
    }, 'Key path resolving past throwing getter rethrows');

    // Value should be cloned before key path is evaluated,
    // and enumerable getter will rethrow.
    const s3 = db.createObjectStore('s3',
        {keyPath: 'throws', autoIncrement: true});
    assert_throws_exactly(err, () => {
      s3.put(o);
    }, 'Key injectability test at throwing getter should rethrow');

    // Value should be cloned before key path is evaluated,
    // and enumerable getter will rethrow.
    const s4 = db.createObjectStore('s4',
        {keyPath: 'throws.x', autoIncrement: true});
    assert_throws_exactly(err, () => {
      s4.put(o);
    }, 'Key injectability test past throwing getter should rethrow');
  },
  (t, db) => {
    t.done();
  },
  'Key path evaluation: Exceptions from enumerable getters'
);

indexeddb_test(
  (t, db) => {
    // Implemented as function wrapper to clean up
    // immediately after use, otherwise it may
    // interfere with the test harness.
    function with_proto_getter(f) {
      return function() {
        Object.defineProperty(Object.prototype, 'throws', {
          get: throwingGetter,
          enumerable: false, configurable: true
        });
        try {
          f();
        } finally {
          delete Object.prototype['throws'];
        }
      };
    }

    // Value should be cloned before key path is evaluated,
    // and non-enumerable getter will be ignored. The clone
    // will have no own property, so key path evaluation will
    // fail and DataError should be thrown.
    const s1 = db.createObjectStore('s1', {keyPath: 'throws'});
    assert_throws_dom('DataError', with_proto_getter(function() {
      s1.put({});
    }), 'Key path resolving to no own property throws DataError');

    // Value should be cloned before key path is evaluated,
    // and non-enumerable getter will be ignored. The clone
    // will have no own property, so key path evaluation will
    // fail and DataError should be thrown.
    const s2 = db.createObjectStore('s2', {keyPath: 'throws.x'});
    assert_throws_dom('DataError', with_proto_getter(function() {
      s2.put({});
    }), 'Key path resolving past no own property throws DataError');

    // Value should be cloned before key path is evaluated,
    // and non-enumerable getter will be ignored. The clone
    // will have no own property, so key path evaluation will
    // fail and injection can succeed.
    const s3 = db.createObjectStore('s3',
        {keyPath: 'throws', autoIncrement: true});
    assert_equals(s3.put({}).readyState, 'pending',
                  'put should not throw due to inherited property');

    // Value should be cloned before key path is evaluated,
    // and non-enumerable getter will be ignored. The clone
    // will have no own property, so key path evaluation will
    // fail and injection can succeed.
    const s4 = db.createObjectStore('s4',
        {keyPath: 'throws.x', autoIncrement: true});
    assert_equals(s4.put({}).readyState, 'pending',
                  'put should not throw due to inherited property');
  },
  (t, db) => {
    t.done();
  },
  'Key path evaluation: Exceptions from non-enumerable getters on prototype'
);

indexeddb_test(
  (t, db) => {
    // Implemented as function wrapper to clean up
    // immediately after use, otherwise it may
    // interfere with the test harness.
    function with_proto_getter(f) {
      return () => {
        Object.defineProperty(Object.prototype, 'throws', {
          get: throwingGetter,
          enumerable: true, configurable: true
        });
        try {
          f();
        } finally {
          delete Object.prototype['throws'];
        }
      };
    }

    // Value should be cloned before key path is evaluated.
    // The clone will have no own property, so key path
    // evaluation will fail and DataError should be thrown.
    const s1 = db.createObjectStore('s1', {keyPath: 'throws'});
    assert_throws_dom('DataError', with_proto_getter(function() {
      s1.put({});
    }), 'Key path resolving to no own property throws DataError');

    // Value should be cloned before key path is evaluated.
    // The clone will have no own property, so key path
    // evaluation will fail and DataError should be thrown.
    const s2 = db.createObjectStore('s2', {keyPath: 'throws.x'});
    assert_throws_dom('DataError', with_proto_getter(function() {
      s2.put({});
    }), 'Key path resolving past throwing getter rethrows');

    // Value should be cloned before key path is evaluated.
    // The clone will have no own property, so key path
    // evaluation will fail and injection can succeed.
    var s3 = db.createObjectStore('s3',
        {keyPath: 'throws', autoIncrement: true});
    assert_equals(s3.put({}).readyState, 'pending',
                  'put should not throw due to inherited property');

    // Value should be cloned before key path is evaluated.
    // The clone will have no own property, so key path
    // evaluation will fail and injection can succeed.
    var s4 = db.createObjectStore('s4',
        {keyPath: 'throws.x', autoIncrement: true});
    assert_equals(s4.put({}).readyState, 'pending',
                  'put should not throw due to inherited property');
  },
  (t, db) => {
    t.done();
  },
  'Key path evaluation: Exceptions from enumerable getters on prototype'
);

indexeddb_test(
  (t, db) => {
    const store = db.createObjectStore('store');
    store.createIndex('index', 'index0');
  },
  (t, db) => {
    const tx = db.transaction('store', 'readwrite', {durability: 'relaxed'});

    const array = [];
    array[99] = 1;

    // Implemented as function wrapper to clean up
    // immediately after use, otherwise it may
    // interfere with the test harness.
    let getter_called = 0;
    function with_proto_getter(f) {
      const prop = '50';
      Object.defineProperty(Object.prototype, prop, {
        enumerable: true, configurable: true,
        get: () => {
          ++getter_called;
          return 'foo';
        }
      });
      try {
        return f();
      } finally {
        delete Object.prototype[prop];
      }
    }

    const request = with_proto_getter(
      () => tx.objectStore('store').put({index0: array}, 'key'));
    request.onerror = t.unreached_func('put should not fail');
    request.onsuccess = t.step_func(function() {
      assert_equals(getter_called, 0, 'Prototype getter should not be called');
      t.done();
    });
  },
  'Array key conversion should not invoke prototype getters'
);

</script>
